/*
    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/
/*!
 * \file msUnitsManager.h
 * \brief define the msUnit and msUnitsManager classes
 * \author G.Reinisch
 * \date 2011
 */

#ifndef msUNITS_H
#define msUNITS_H

#include <msTreeMapper.h>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include<boost/algorithm/string/replace.hpp>


namespace atomism{

using namespace std;
class msPhysicalInterface;
    
namespace strunit {    
     enum UnitType { dKinEner , dJ , dP };
}

class msUnitsManager;

/*! \class msUnit
 *
 * \brief Store the unit of a variable
 *    
 * msUnit represent a unit as maps between unit-symbols and a power.
 * Unit-symbols can be of primitive types *Length*, *Angle*, *Energy*, *Mass*, 
 * *Time* and *Temperature*. \n
 * Different variable's types are predefined (uTypeVar) in terms of
 * relation between unit-symbols and power.
 * Note that the decompositions are not unique,
 * and the user must ensure that the decomposition proposed for this predefined 
 * variables is coherent with its expectations. \n
 * This class does not do much more: the transformations between 
 * units are managed by the class msUnitsManager. 
 * 
 * \section string   String representation, lexical cast
 *
 * The unit can be defined from a string of appropriate format, it is of the 
 * form:
 *
 *       StrA^i.StrB^j.StrC^k
 * where StrA, StrB, StrC are the string representation of the unit symbols (see
 * @ref newUnit2), and i,j,k their associated power.
 * 
 * \section newUnit  Defining a new unit symbol
 * 
 * The definition of a new symbol of unit is achieved in two steps:
 *     - defining the symbol's name in the appropriate enum list. 
 * For instance to define the *BigMac/mol* as a unit of energy you
 * should complete the *uEnergy* like this (we use the id BigMac_mol):
 * ~~~~~~~~~~~~~{.cpp}
  enum uEnergy { J , kJ , cal , kcal , hartree , cm_1 , eV, J_mol , kJ_mol , cal_mol , kcal_mol, BigMac_mol };
 * ~~~~~~~~~~~~~
 *     - to define the conversion factor to the corresponding SI unit. This is
 * done in the msUnitsManager class, see  @ref newUnit2 .
 *
 *
 * \section newVarType  Defining a new variable type
 * 
 * You need to:
 *     - complete the enum list uTypeVar with a new variable type, and complete
 * the function set(uTypeVar typeVar, const msUnitsManager& unitsystem)
 *     - provide the documentation of your new definition in the doxygen documentation of
 * set(uTypeVar typeVar, const msUnitsManager& unitsystem). 
 *     - to export the type in python: complete the enum list in the registryInPython() 
 * method. 
 *
*/
class msUnit : public msTreeMapper
{ 
    friend class msUnitsManager;
    friend class msParamsManager;
    
//! @name Registration, see msRegister
    //@{
    static msRegister* createInstance() {  return new msUnit; }
    static msRegistrar Registrar;
    static bool isUnitRegisteredInPython;
    void registryInPython();
    //@}      
public:
  
    enum uTypeVar { vLength , vAngle, vEnergy , vMass, vTime, vTemperature, vVelocity, 
                    vAngularVelocity, vLinearMomentum, vAngularMomentum, vKineticEnergy };

//! @name Attributs
    //@{
    enum uLength { Angstrom , Bohr , nm , m , NO_OF_LENGTH_MAX };
    enum uEnergy { J , kJ , cal , kcal , hartree , cm_1 , eV, J_mol , kJ_mol , cal_mol , kcal_mol, NO_OF_ENERGY_MAX };
    enum uQuantity { mol , molecule , NO_OF_QUANTITY_MAX};
    enum uMass { amu , g , kg , g_mol , kg_mol, NO_OF_MASS_MAX};
    enum uTime { ps , ns , micros , s, NO_OF_TIME_MAX };
    enum uTemperature { K , C , NO_OF_TEMPERATURE_MAX };
    enum uAngle { Degree , Radian, NO_OF_ANGLE_MAX };
    //@}
   
protected:
//! @name Tree structure definition, see msTreeMapper
    //@{ 	   
     void initialize() {  
       
        msTreeMapper::initialize(); 
	declareAttribute(Expression,"Expression"); 
     }
     void update(){   
        msTreeMapper::update();
	set(Expression);
     }
     //@}
public:

     static boost::shared_ptr<msUnit> New(std::string expr="") {

 	      boost::shared_ptr<msUnit> T( new msUnit(expr) );
	      LOGGER_ENTER_FUNCTION_DBG("msUnit::New()",T->getFullId());
	      T->initialize(); T->update();
	      LOGGER_EXIT_FUNCTION();
	      return T;
	      }

     virtual boost::shared_ptr<msTreeMapper> clone() { 
            boost::shared_ptr<msUnit> clone = New(); 
	    clone->msTreeMapper::operator=(*this);					      
	    return clone;
            }  
      
      //! set a 'no unit' state
      msUnit() : msTreeMapper() { constructVar("msUnit","Unit","unit");  setNull();  };
      
      //! set from a string, see @ref string
      msUnit(string expr): msTreeMapper(){ constructVar("msUnit","Unit","unit"); set(expr); };

      //! set a 'no unit' state
      void setNull();
      
      //! set from a string, see @ref string
      void set(string line);

      /** \brief Set from a variable type and a units manager
 * ~~~~~~~~~~~~~{.cpp}
 The decomposition proposed are:
    case vLength: 
          Length=pair<	  uLength,int>( 	unitsystem.Length , 1 );
    case vEnergy: 	
          Energy=pair<	  uEnergy,int>( 	unitsystem.Energy , 1 );  
    case vMass: 	
          Mass=pair<	  uMass,int>( 	     	unitsystem.Mass ,   1 );
    case vTemperature:  
          Temperature=pair< uTemperature,int>( 	unitsystem.Temperature , 1 );  
    case vTime: 	
          Time=pair<	  uTime,int>( 		unitsystem.Time ,   1 );
    case vAngle: 	
          Angle=pair<	  uAngle,int>( 		unitsystem.Angle ,  1 );
    case vVelocity: 	
          Length=pair<	  uLength,int>( 	unitsystem.Length , 1 );  
	  Time=pair<	  uTime,int>( 		unitsystem.Time ,  -1 );
    case vAngularVelocity: 
	  Angle=pair<	  uAngle,int>( 		unitsystem.Angle ,  1 ); 
	  Time=pair<	  uTime,int>( 		unitsystem.Time ,  -1 );
    case vLinearMomentum: 
	  Mass=pair<	  uMass,int>( 	     	unitsystem.Mass ,   1 );
	  Length=pair<	  uLength,int>( 	unitsystem.Length , 2 ); 
          Time=pair<	  uTime,int>( 		unitsystem.Time ,  -2 );
    case vAngularMomentum: 
	  Mass=pair<	  uMass,int>( 	     	unitsystem.Mass ,   1 );
	  Length=pair<	  uLength,int>( 	unitsystem.Length , 2 ); 
	  Angle=pair<	  uAngle,int>( 		unitsystem.Angle ,  1 ); 
          Time=pair<	  uTime,int>( 		unitsystem.Time ,  -1 );
    case vKineticEnergy: 
	  Mass=pair<	  uMass,int>( 	     	unitsystem.Mass ,   1 );
	  Length=pair<	  uLength,int>( 	unitsystem.Length , 2 ); 
          Time=pair<	  uTime,int>( 		unitsystem.Time ,  -2 );
 * ~~~~~~~~~~~~~
      */
      void set(uTypeVar typeVar, const msUnitsManager& unitsystem);

      //! equal the powers of the unit to those of a given msUnit object
      void setDimFrom(const msUnit& u);
      
      //! return the string representation
      string getStr() const;
      
      //! return true if the object does not have unit
      bool isNoneUnit() const;
      
      //! return true if *this* is homogeneous with the given unit object
      bool isConsistent(const msUnit& unit) const;
      
      std::ostream& print(std::ostream& out) const;
	
      msUnit& operator=(const msUnit& rhs){ msTreeMapper::operator=(rhs); Expression=rhs.Expression; return *this; }
             
      //! keep the power, but change the symbols to those define in the given unitsystem
      msUnit& reset(const msUnitsManager& unitsystem);
       
private:
  
      std::string Expression;
      
      pair<uLength,int>         Length;
      pair<uEnergy,int>         Energy;
      pair<uQuantity,int>       Quantity;
      pair<uMass,int>           Mass;
      pair<uTime,int>           Time;
      pair<uTemperature,int>    Temperature;
      pair<uAngle,int>          Angle;  
};
ostream& operator<<(ostream&,const msUnit&);


/*! \class msUnitsManager
 * 
 * \brief Manager of units system, used to handle conversions
 * 
 *
 * The msUnitsManager class defines the units system used by the objects deriving
 * from msPhysicalInterface. 
 * Its objective is to simply and clarify the use of unit system; in particular 
 * the default behavior of msPhysicalInterface derived objects is to interpret inputs 
 * and provide outputs using  the units system defined by the units manager.
 * The class provides also a lot of conversion functions. \n
 * The msPhysicalInterface objects are referenced in their msUnitsManager member
 * in the 'LinkedObjects' attribute.
 * Each time the units system is changed, the msUnitsManager class calls the 
 * method msPhysicalInterface::updateUnits with the old and new units system.
 * This allows to implement the proper behavior to ensure that the
 * msPhysicalInterface object is always up to date with respect to its units 
 * manager state. 
 * 
 * 
 * \section newUnit2  Defining new units
 * 
 * As we explained in the msUnit documentation, the definition of a new symbol 
 * of unit is achieved in two steps:
 *     - defining the symbol's id in the appropriate enum list. 
 * We have shown how to define the *BigMac/mol* symbol unit (as the enum 
 * element *BigMac_mol*, see @ref newUnit). 
 *     - defining the conversion factor to the associated SI unit. 
 * To do so, you need to add an element to the corresponding map which associates
 * to the symbol's name :
 *          - a name (string representation) 
 *          - a conversion factor with respect to the SI unit. 
 * For  our BigMac/mol  unit, this is achievd by adding this line into the
 * method initMaps:
 * ~~~~~~~~~~~~~{.cpp}
 MapStrEnergy["BigMac/mol"] = pair<msUnit::uEnergy,double>( msUnit::BigMac_mol , 495 * 4186.7951  / 6.02214129e23 );
 * ~~~~~~~~~~~~~
 * (495 kcal in the energetic value of a Big Mac). In the same way Length, Angle, Temperature, Time and Mass
 * units can be completed.
 */ 
class msUnitsManager : public msTreeMapper 
{       
        friend class msPhysicalInterface;
        
        static msRegister* createInstance() { return new msUnitsManager; }
        static msRegistrar Registrar;
        static bool isUnitsManagerRegisteredInPython;
        void registryInPython();
        	
public:

	static double Convert(msUnit::uEnergy source,double v,msUnit::uEnergy dest);
        static double Convert(msUnit::uMass source  ,double v,msUnit::uMass dest);
        static double Convert(msUnit::uLength source,double v,msUnit::uLength dest);
        static double Convert(msUnit::uTime source,double v,msUnit::uTime dest);
        static double Convert(msUnit::uTemperature source,double v,msUnit::uTemperature dest);
        static double Convert(msUnit::uAngle source,double v,msUnit::uAngle dest);
       
        static string getStr(msUnit::uLength);
        static string getStr(msUnit::uEnergy);
        static string getStr(msUnit::uTime);
        static string getStr(msUnit::uMass);
        static string getStr(msUnit::uAngle);
        static string getStr(msUnit::uTemperature);
        static string getStr(msUnit::uQuantity);
        
        static map<string,pair<msUnit::uLength,double> >        MapStrLength;
        static map<string,pair<msUnit::uEnergy,double> >        MapStrEnergy;
        static map<string,pair<msUnit::uQuantity,double> >      MapStrQuantity;
        static map<string,pair<msUnit::uMass,double> >          MapStrMass;
        static map<string,pair<msUnit::uTime,double> >          MapStrTime;
        static map<string,pair<msUnit::uTemperature,double> >   MapStrTemperature;
        static map<string,pair<msUnit::uAngle,double> >         MapStrAngle;
        
        static bool _is_init;
	
	static std::vector<std::string> listUnitsAvailable(msUnit::uTypeVar v);
	
        void printUnityMap();
         
        static bool initMaps() {
           MapStrLength["Angstrom"]	=pair<msUnit::uLength,double>(         msUnit::Angstrom , 1e-10           	);
           MapStrLength["Bohr"]		=pair<msUnit::uLength,double>(             msUnit::Bohr , 5.2918e-11      	);
           MapStrLength["nm"]		=pair<msUnit::uLength,double>(               msUnit::nm , 1e-9            	);
           MapStrLength["m"]		=pair<msUnit::uLength,double>(                msUnit::m , 1.              	);

           MapStrEnergy["J"]		=pair<msUnit::uEnergy,double >(               msUnit::J , 1.              	);
           MapStrEnergy["kJ"]		=pair<msUnit::uEnergy,double >(              msUnit::kJ , 1e3             	);
           MapStrEnergy["cal"]		=pair<msUnit::uEnergy,double>(              msUnit::cal , 4.1867951          	);
           MapStrEnergy["kcal"]		=pair<msUnit::uEnergy,double>(             msUnit::kcal , 4186.7951         	);
           MapStrEnergy["hartree"]	=pair<msUnit::uEnergy,double>(          msUnit::hartree , 4.35974434e-18  	);
           MapStrEnergy["cm-1"]		=pair<msUnit::uEnergy,double>(             msUnit::cm_1 , 1.98630e-23     	);
           MapStrEnergy["eV"]		=pair<msUnit::uEnergy,double>(               msUnit::eV , 1.60217657e-19	     	);
           MapStrEnergy["J/mol"]	=pair<msUnit::uEnergy,double >(           msUnit::J_mol , 1./ 6.02214129e23       );
           MapStrEnergy["kJ/mol"]	=pair<msUnit::uEnergy,double >(          msUnit::kJ_mol , 1e3 / 6.02214129e23     );
           MapStrEnergy["cal/mol"]	=pair<msUnit::uEnergy,double>(          msUnit::cal_mol , 4.1867951  / 6.02214129e23 );
           MapStrEnergy["kcal/mol"]	=pair<msUnit::uEnergy,double>(         msUnit::kcal_mol , 4186.7951  / 6.02214129e23 );

           MapStrQuantity["mol"]		=pair<msUnit::uQuantity,double>(            msUnit::mol , 6.02214129e23   	);
           MapStrQuantity["unity"]	=pair<msUnit::uQuantity,double>(       msUnit::molecule , 1.              	);

           MapStrMass["amu"]		=pair<msUnit::uMass,double>(                msUnit::amu , 1.6605402E-27   	);
           MapStrMass["g"]		=pair<msUnit::uMass,double>(                  msUnit::g , 1e-3            	);
           MapStrMass["kg"]		=pair<msUnit::uMass,double>(                 msUnit::kg , 1.              	);
           MapStrMass["g/mol"]		=pair<msUnit::uMass,double>(              msUnit::g_mol , 1e-3 / 6.02214129e23    );
           MapStrMass["kg/mol"]		=pair<msUnit::uMass,double>(             msUnit::kg_mol , 1.   / 6.02214129e23    );

           MapStrTime["ps"]		=pair<msUnit::uTime,double>(                 msUnit::ps , 1e-12           	);
           MapStrTime["ns"]		=pair<msUnit::uTime,double>(                 msUnit::ns , 1e-9            	);
           MapStrTime["micros"]		=pair<msUnit::uTime,double>(             msUnit::micros , 1e-3            	);
           MapStrTime["s"]		=pair<msUnit::uTime,double>(                  msUnit::s , 1.              	);

           MapStrTemperature["K"]	=pair<msUnit::uTemperature,double>(           msUnit::K , 1.              	);

           MapStrAngle["Degree"]	=pair<msUnit::uAngle,double>(            msUnit::Degree , 3.14159265/180. 	);
           MapStrAngle["Radian"]	=pair<msUnit::uAngle,double>(            msUnit::Radian , 1.      	) ;
           return 1;
        }
protected:
  	
        void initialize() { 
	   
	   msTreeMapper::initialize();
	   msTreeMapper::declareAttribute(Expr,"Expr");
	}
	
	void update(){ 
   
        msTreeMapper::update(); set(Expr);
        }
public:
      
       static boost::shared_ptr<msUnitsManager> New(std::string expr="") {
	  
	  boost::shared_ptr<msUnitsManager> T( new msUnitsManager );
	  if(expr!="") T->set(expr);
	  LOGGER_ENTER_FUNCTION_DBG("msUnitsManager::New()",T->getFullId());
	  T->initialize(); T->update();
	  LOGGER_EXIT_FUNCTION();
	  return T; 
       }
       virtual boost::shared_ptr<msTreeMapper> clone() { 
            boost::shared_ptr<msUnitsManager> clone = New(); 
	    clone->msTreeMapper::operator=(*this);					      
	    return clone;
            }   

        msUnitsManager() : msTreeMapper() { 
	  
	   if(!_is_init) initMaps();
	   _is_init=1;
           constructVar("msUnitsManager","Units","units");  setSI();
	};

        void set( string line );
                
        void setSI();

	string getExpression() { return Expr; }

        std::ostream& print(std::ostream& out) const; 

        std::ostream& abstract(std::ostream& out) const; 
	
        void listWhoUsingMe();

//! @name Attributs
      //@{
        msUnit::uLength Length;
        msUnit::uEnergy Energy;
        msUnit::uQuantity Quantity;
        msUnit::uMass Mass;
        msUnit::uTime Time;
        msUnit::uTemperature Temperature;
        msUnit::uAngle Angle;
        //@}

//! @name return a string representation
      //@{
        string getStr(msUnit::uTypeVar typevar) const;

        string getStrFrom(const msUnit&) const;
        string getEnergyStr() const;
        string getLengthStr() const; 
        string getTimeStr()   const;
        string getMassStr()   const;
        string getAngleStr()  const;
        string getTempStr()   const;
        string getQuantityStr() const;
        string getInertiaStr() const;
        //@}

/** @name Convert a value specified by a unit 'source' to the current unit system
 */
       //@{       
        double convert(const msUnit& source , double a) 	const;
        double convert(std::string source, double a) 		const;
        double convert(const msPhysicalInterface& source,
		       msUnit::uTypeVar, double a) 		const;
        
        double convert(msUnit::uEnergy source,double v) 	const;
        double convert(msUnit::uMass source,double v) 		const;
        double convert(msUnit::uLength source,double v) 	const;
        double convert(msUnit::uTime source,double v) 		const;
        double convert(msUnit::uTemperature source,double v) 	const;
        double convert(msUnit::uAngle source,double v) 		const;
        double convert(msUnit::uQuantity source,double v) 	const;
        //@}
/** @name Convert a value from the current unit system to the specified units 'target'
 */
       //@{  
	double convertTo(double v,msUnit::uEnergy target)	const 	{ return v/convert(target,1); 
									 }
	double convertTo(double v,msUnit::uMass target) 	const   { return v/convert(target,1); 
									 }
	double convertTo(double v,msUnit::uLength target) 	const 	{ return v/convert(target,1); 
									 }
	double convertTo(double v,msUnit::uTime target) 	const   { return v/convert(target,1); 
									 }  
	double convertTo(double v,msUnit::uTemperature target) 	const 	{ return v/convert(target,1); 
									 }
	double convertTo(double v,msUnit::uAngle target) 	const  	{ return v/convert(target,1); 
									 }
	double convertTo(double v,msUnit::uQuantity target) 	const   { return v/convert(target,1); 
									 }
	double convertTo(double v,const msUnit& target) 	const   { return v/convert(target,1); 
									 }
         //@}      
        
	private:

        string Expr; 			//!< Store the expression value defined in a node

        void updateObjectsUsingMe(msUnitsManager& Old, msUnitsManager& New);
  
        void addObjectUsingMe(boost::shared_ptr<msPhysicalInterface> obj);

        std::vector<boost::weak_ptr<msPhysicalInterface> > LinkedObjects;

        void updateExpr();
	void initExpr(string line);
};

ostream& operator<<(ostream&,const msUnitsManager&);
              
}
#endif // msUNITS_H
